<html>
<head>
  <title>11.享元（Flyweight）模式</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="22997"/>
<h1>11.享元（Flyweight）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/6/28 11:06</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 0:09</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>享元模式运用共享技术有效的支持大量细粒度的对象。</div></li><li><div>又称为蝇量模式。</div></li><li><div>flyweight模式描述了如何共享对象，使得可以细粒度的使用它们而不需要高昂的代价。</div></li><li><div>flyweight不能对它所运行的场景做出任何假设，这里的关键概念是内部状态和外部状态之间的区别。<span style="font-weight: bold;">内部状态</span>存储于flyweight当中，它包含了独立于flyweight场景的信息，这些信息使得flyweight可以被共享。而外部状态取决于flyweight场景，并根据场景而变化，因此不可共享。用户对象负责在必要的时候讲外部状态传递给flyweight。</div></li><li><div>Flyweight模式对那些通常由于数量太大而难以用对象来表示的概念或实体进行建模。</div></li><li><div>Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。</div></li><li><div>flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中，而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时，将状态传递给它。</div></li><li><div>用户不应直接对ConcreteFlyweight类进行实例化，而只能从FlyweightFactory对象得到ConcreteFlyweight对象，这可以保证对它们适当的进行共享。</div></li><li><div>共享还意味着某种形式的引用计数和垃圾回收，这样当一个flyweight不再使用时，可以回收它的存储空间。然而，当flyweight的数目固定而且很小的时候，这两种操作都不必要。在这种情况下，flyweight完全可以永久保存。</div></li><li><div>享元模式可以避免大量非常相似类的开销，在程序设计中，有事需要生成大量的细粒度的类实例来表示数据，如果能发现这些实例除了几个参数外基本相同，有事可大幅减少需实例化的类的数量，如果能把那些参数移到类实例的外面，在方法调用时将它们传递过来，就可以通过共享，大幅减少单个实例的数目。</div></li><li><div><span style="font-weight: bold;">适用</span>：如果一个应用程序使用了大量的对象，而大量对象造成了很大的存储开销时，就应该考虑使用；另外，对象的大多数状态可以是外部状态，如果删除对象的外部状态，可以用相对较少的共享对象取代很多组对象，此时可以考虑使用享元模式。</div></li><li><div>享元模式更多时是一种底层设计模式，在现实中也有应用，如各种棋类游戏。</div></li><li><div>享元模式提出了两个要求：细粒度的对象和共享对象。</div></li><li><div>Flyweight是拳击比赛中的特有名词，意思是“特轻量级”，指的是51公斤级的比赛，用到设计模式中是指类要轻量级，粒度要小，这才是Flyweight要表达的意思，粒度小了，带来的问题是对象太多，那就用共享技术来解决。所以享元模式也被称为轻量级模式。</div></li><li><div>正因为区分了内部状态和外部状态，可以将具有相同内部状态的对象存储在享元池中，享元池中的对象是可以实现共享的，需要的时候就将对象从享元池中取出，实现对象的复用，通过向取出对象注入不同的外部状态，可以得到一系列相似的对象，而这些对象在内存中实际上只存储一份。</div></li><li><div>如想让某个类的一个实例能够用来提供许多“虚拟实例”，就使用蝇量模式。</div></li><li><div>当一个类有许多实例，而这些实例能被同一方法控制的时候，我们就可以使用蝇量模式。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div><img src="11.享元（Flyweight）模式_files/Image.png" type="image/png" data-filename="Image.png" width="401"/></div><ul><li><div>Flyweight：描述一个接口，通过这个接口，flyweight可以接受并作用于外部状态。这个类是所有享元类的超类或接口。接受一个外部状态（类图中的extrinsicState），然后针对外部状态进行一些操作（类图中的Operation操作）。这是一个抽象类，可以把外部状态和内部状态定义出来，避免子类的随意扩展。享元类的设计是享元模式的要点之一，在享元类中要将内部状态与外部状态分开处理，通常内部状态作为享元类的成员变量，外部状态通过注入的方法添加到享元类中。</div></li><li><div>ConcreteFlyweight：实现Flyweight接口，并未内部状态（如果有的话）增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的，即它必须独立于ConcreteFlyweight对象的场景。具体享元角色，实现抽象角色定义的业务，需注意内部状态的处理应与环境无关，不应出现一个操作改变了内部状态，同时修改外部状态。</div></li><li><div>UnsharedConcreteFlyweight：并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能，但它并不强制共享。在flyweight对象结构的某些层次，UNsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。不存在内部状态或安全要求（线程安全），不能够使用共享技术的对象，一般不出现在享元工厂中。</div></li><li><div>FlyweightFactory：享元工厂。创建并管理flyweight对象。确保合理的共享flyweight。当用户请求一个flyweight时，FlyweightFactory对象提供一个已创建的实例或者创建一个新的实例（如果不存在的话）。构建一个池容器，同时提供从池中获得对象的方法。主要目的是提供一个用于存储享元对象的享元池。</div></li><li><div>Client：维持一个对flyweight的引用。计算或存储一个（多个）flyweight的外部状态。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>使用享元模式，有共享的对象，实例总数大大减少，如果共享的状态越多，存储节约也越多，节约量随共享状态的增多而增大。可以大大减少应用程序创建的对象，降低程序内存占用，增强程序的性能。</div></li><li><div>享元模式的外部状态相对独立，而且不会影响其内部状态，从而使得享元对象可以在不同的环境中被共享。</div></li><li><div>将许多“虚拟”对象的状态集中管理。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>使用享元模式需要维护一个记录了系统已有的所有享元的列表，这本身需消耗资源，另外享元模式使得系统更加复杂，为了使对象可以共享，需要将一些状态外部化，这使程序的逻辑复杂化。</div></li><li><div>为了使对象可以共享，享元模式需要将享元对象的部分状态外部化，从而读取外部状态将使得运行时间变长。</div></li><li><div>一旦你实现了蝇量模式，那么单个的逻辑实例将无法拥有独立而不同的行为。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、个人理解</span></div><ul><li><div>使用享元时，思路与抽象很重要，如何具体实现反而比较灵活，相对不太重要。也就是说，享元模式的思想比起如何实现更加重要。</div></li><li><div>享元模式的核心目标是节省存储空间，可以算是用时间换取空间，但是特定情况下（比如内存使用量很大，发生颠簸的时候），节省空间也就是节省时间。</div></li><li><div>享元模式比较复杂，要区分内部状态与外部状态，其实现更加复杂。、</div></li><li><div>在享元模式中，由于对象的共享，一定要考虑到线程安全的问题。</div></li><li><div>应用享元模式时，需要考虑两个维度：1）同时继承Flyweight抽象类的有ConcreteFlyweight和UnsharedConcreteFlyweight，即有可以共享的类和不可以共享的类。2）在ConcreteFlyweight类，即可以共享的类中，有可以共享的内部状态（intrinsicState）和不可以共享的外部状态（extrinsicState）。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div></body></html> 